﻿using Quartz;
using Quartz.Impl;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using TiaoTiaoCode.Quartzer.Entities;

namespace TiaoTiaoCode.Quartzer
{
    /// <summary>
    /// 
    /// </summary>
    public class TiaoTiaoQuartz
    {
        #region

        protected static ISqlSugarClient? _scope;

        public static IScheduler? Scheduler { get; private set; }

        #endregion

        #region 持久化数据库初始

        /// <summary>
        /// 初始持久化
        /// </summary>
        /// <param name="dbType"></param>
        /// <param name="connectStr"></param>
        static void PersistenceDb(SqlSugar.DbType dbType, string connectStr)
        {
            var scope = new SqlSugarScope(new ConnectionConfig
            {
                DbType = dbType,
                ConnectionString = connectStr,
                IsAutoCloseConnection = true,
            });

            PersistenceDb(scope);
        }

        /// <summary>
        /// 初始持久化
        /// </summary>
        /// <param name="scope"></param>
        static void PersistenceDb(ISqlSugarClient scope)
        {
            _scope?.Close();
            _scope?.Dispose();

            _scope = scope;

            Stream? stream = null;
            switch (scope.CurrentConnectionConfig.DbType)
            {
                case SqlSugar.DbType.MySql:
                    stream = Assembly.GetAssembly(typeof(TiaoTiaoQuartz)).GetManifestResourceStream("TiaoTiaoCode.Quartzer.TableSentence.mysql.sql");
                    break;
                case SqlSugar.DbType.Sqlite:
                    stream = Assembly.GetAssembly(typeof(TiaoTiaoQuartz)).GetManifestResourceStream("TiaoTiaoCode.Quartzer.TableSentence.sqlite.sql");
                    break;
                case SqlSugar.DbType.SqlServer:
                    var version = scope.Ado.SqlQuery<string>("select @@version").First();
                    version = version.Split("-", StringSplitOptions.RemoveEmptyEntries).First();

                    var regex = new Regex(@"\d+");
                    version = regex.Match(version).Value;

                    if (int.Parse(version) <= 2016)
                        stream = Assembly.GetAssembly(typeof(TiaoTiaoQuartz)).GetManifestResourceStream("TiaoTiaoCode.Quartzer.TableSentence.sqlserver_below2016.sql");
                    else
                        stream = Assembly.GetAssembly(typeof(TiaoTiaoQuartz)).GetManifestResourceStream("TiaoTiaoCode.Quartzer.TableSentence.sqlserver.sql");
                    break;
            }

            if (stream == null) return;

            if (!CheckCanPersistence(scope))
            {
                InitDb(scope);
                return;
            }

            using var sr = new StreamReader(stream);
            var sql = sr.ReadToEnd();
            scope.Ado.ExecuteCommand(sql);
            InitDb(scope);
        }

        /// <summary>
        /// 检测是否可以初始持久化库
        /// 当有计划任务时，不执行持久化库初始操作
        /// </summary>
        /// <param name="scope"></param>
        /// <returns></returns>
        static bool CheckCanPersistence(ISqlSugarClient scope)
        {
            var t = scope.DbMaintenance.GetTableInfoList().Where(t => t.Name == "quartz_jobs").FirstOrDefault();
            if (t == null) return true;

            var count = scope.Queryable<QuartzJobs>().Count();
            return count <= 0;
        }

        #endregion

        #region 任务管理初始化

        /// <summary>
        /// 初始任务管理
        /// </summary>
        /// <param name="scope"></param>
        static void InitDb(ISqlSugarClient scope)
        {
            scope.CodeFirst.InitTables<QuartzJobs>();
        }

        #endregion

        #region 初始化Quartz

        /// <summary>
        /// 初始Quartz
        /// </summary>
        public static void Init(SqlSugar.DbType dbType, string connectString, string schedulerName = "MyScheduler", int threadCount = 20)
        {
            NameValueCollection pars = new NameValueCollection
            {
                //scheduler名字
                ["quartz.scheduler.instanceName"] = schedulerName,
                //线程池个数
                ["quartz.threadPool.threadCount"] = threadCount.ToString(),

                //类型为JobStoreXT,事务
                ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",

                //数据源名称
                ["quartz.jobStore.dataSource"] = "myDS",
                //数据表名前缀
                ["quartz.jobStore.tablePrefix"] = "QRTZ_",
                //使用Sqlserver的Ado操作代理类
                ["quartz.jobStore.driverDelegateType"] = dbType switch
                {
                    SqlSugar.DbType.SqlServer => "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
                    SqlSugar.DbType.MySql => "Quartz.Impl.AdoJobStore.MySQLDelegate, Quartz",
                    SqlSugar.DbType.Sqlite => "Quartz.Impl.AdoJobStore.SQLiteDelegate, Quartz",
                    _ => "Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz"
                },
                //数据源连接字符串
                ["quartz.dataSource.myDS.connectionString"] = connectString,
                //数据源的数据库
                ["quartz.dataSource.myDS.provider"] = dbType switch
                {
                    SqlSugar.DbType.Sqlite => "SQLite",
                    SqlSugar.DbType.SqlServer => "SqlServer",
                    SqlSugar.DbType.MySql => "MySql",
                    _ => "SqlServer"
                },

                //序列化类型
                ["quartz.serializer.type"] = "json",//binary

                //自动生成scheduler实例ID，主要为了保证集群中的实例具有唯一标识
                ["quartz.scheduler.instanceId"] = "AUTO",
                //是否配置集群
                ["quartz.jobStore.clustered"] = dbType switch
                {
                    DbType.Sqlite => "false",
                    _ => "true"
                },
            };

            StdSchedulerFactory factory = new StdSchedulerFactory(pars);
            //IScheduler scheduler = factory.GetScheduler().Result;
            Scheduler = factory.GetScheduler().Result;
        }

        #endregion

        #region Schedular操作

        /// <summary>
        /// 启动计划任务
        /// </summary>
        public static async void Start()
        {
            if (Scheduler!.IsShutdown)
                Init(_scope!.CurrentConnectionConfig.DbType, _scope!.CurrentConnectionConfig.ConnectionString);

            await Scheduler!.Start();

            var jobs = await _scope!.Queryable<QuartzJobs>().ToListAsync();

            foreach (var job in jobs)
            {
                var jd = await Scheduler!.GetJobDetail(JobKey.Create(job.JobName!, job.JobCode!));
                if (job.JobStatus == "start")
                    await Scheduler!.ResumeJob(jd!.Key);
                else if (job.JobStatus == "pause")
                    await Scheduler!.PauseJob(jd!.Key);
            }

            await _scope.Updateable(jobs).ExecuteCommandAsync();
        }

        /// <summary>
        /// 停止计划任务
        /// </summary>
        public static async void Stop()
        {
            await Scheduler!.Shutdown();
        }

        /// <summary>
        /// 清空所有计划任务
        /// </summary>
        public static void Clear()
        {
            // 清除计划任务
            Scheduler?.Clear();

            // 删除任务数据
            _scope?.Deleteable<QuartzJobs>().ExecuteCommand();
        }

        #endregion

        #region Job操作        

        /// <summary>
        /// 添加任务
        /// </summary>
        /// <returns>JobId</returns>
        public static async Task<string> AddJob<Tjob>(string jobName, string jobCode, string cron) where Tjob : IJob
        {
            var jobs = await _scope!.Queryable<QuartzJobs>().Where(t => t.JobName == jobName && t.JobCode == jobCode).ToListAsync();

            // 有同名的任务
            if (jobs != null && jobs.Count > 0)
            {
                foreach (var j in jobs)
                {
                    await Scheduler!.DeleteJob(JobKey.Create(jobName, jobCode));
                }

                await _scope.Deleteable<QuartzJobs>().ExecuteCommandAsync();
            }

            SchedularAddJob<Tjob>(jobName, jobCode, cron);

            var id = SnowFlakeSingle.Instance.NextId().ToString();
            await _scope!.Insertable(new QuartzJobs
            {
                Id = id,
                JobName = jobName,
                JobCode = jobCode,
                JobStatus = "pause",
                CreateDate = DateTime.Now,
                DelFlag = "0",
            }).ExecuteCommandAsync();

            return id;
        }

        /// <summary>
        /// 添加任务
        /// </summary>
        /// <returns>JobId</returns>
        public static async Task<string> AddJob(Type jobType, string jobName, string jobCode, string cron)
        {
            var jobs = await _scope!.Queryable<QuartzJobs>().Where(t => t.JobName == jobName && t.JobCode == jobCode).ToListAsync();

            // 有同名的任务
            if (jobs != null && jobs.Count > 0)
            {
                foreach (var j in jobs)
                {
                    await Scheduler!.DeleteJob(JobKey.Create(jobName, jobCode));
                }

                await _scope.Deleteable<QuartzJobs>().ExecuteCommandAsync();
            }

            SchedularAddJob(jobType, jobName, jobCode, cron);

            var id = SnowFlakeSingle.Instance.NextId().ToString();
            await _scope!.Insertable(new QuartzJobs
            {
                Id = id,
                JobName = jobName,
                JobCode = jobCode,
                JobStatus = "pause",
                CreateDate = DateTime.Now,
                CronExpression = cron,
                DelFlag = "0",
            }).ExecuteCommandAsync();

            return id;
        }


        #region 创建任务

        /// <summary>
        /// 添加任务-泛型方式
        /// </summary>
        /// <typeparam name="Tjob"></typeparam>
        /// <param name="jobName"></param>
        /// <param name="jobCode"></param>
        /// <param name="cron"></param>
        static async void SchedularAddJob<Tjob>(string jobName, string jobCode, string cron) where Tjob : IJob
        {
            var jd = JobBuilder.Create<Tjob>()
                       .WithIdentity(jobName, jobCode)
                       .StoreDurably()
                       .Build();

            var trigger = TriggerBuilder.Create()
                            .StartNow()
                            .WithIdentity($"trigger-{jobName}", jobCode)
                            .WithCronSchedule(cron)
                            .Build();

            await Scheduler!.ScheduleJob(jd, trigger);
            await Scheduler!.AddJob(jd, true);
            await Scheduler!.PauseJob(jd.Key);
        }

        /// <summary>
        /// 添加任务-通过Job类型方式
        /// </summary>
        /// <param name="jobType"></param>
        /// <param name="jobName"></param>
        /// <param name="jobCode"></param>
        /// <param name="cron"></param>
        static async void SchedularAddJob(Type jobType, string jobName, string jobCode, string cron)
        {
            var jd = JobBuilder.Create(jobType)
                       .WithIdentity(jobName, jobCode)
                       .StoreDurably()
                       .Build();

            var trigger = TriggerBuilder.Create()
                            .StartNow()
                            .WithIdentity($"trigger-{jobName}", jobCode)
                            .WithCronSchedule(cron)
                            .Build();

            await Scheduler!.ScheduleJob(jd, trigger);
            await Scheduler!.AddJob(jd, true);
            await Scheduler!.PauseJob(jd.Key);
        }

        #endregion

        /// <summary>
        /// 开始任务
        /// </summary>
        /// <param name="jobIds"></param>
        public static async void StartJob(List<string> jobIds)
        {
            var jobs = await _scope!.Queryable<QuartzJobs>().Where(t => jobIds.Contains(t.Id)).ToListAsync();

            foreach (var job in jobs)
            {
                job.JobStatus = "start";
                await Scheduler!.ResumeJob(JobKey.Create(job.JobName!, job.JobCode!));
            }

            await _scope.Updateable(jobs).ExecuteCommandAsync();
        }

        /// <summary>
        /// 暂停任务
        /// </summary>
        /// <param name="jobName"></param>
        /// <param name="jobCode"></param>
        public static async void PauseJob(List<string> jobIds)
        {
            var jobs = await _scope!.Queryable<QuartzJobs>().Where(t => jobIds.Contains(t.Id)).ToListAsync();

            foreach (var job in jobs)
            {
                job.JobStatus = "pause";

                await Scheduler!.PauseJob(JobKey.Create(job.JobName!, job.JobCode!));
            }

            await _scope.Updateable(jobs).ExecuteCommandAsync();
        }

        /// <summary>
        /// 停止任务
        /// </summary>
        /// <param name="jobName"></param>
        /// <param name="jobCode"></param>
        public static async void ResumeJob(List<string> jobIds)
        {
            var jobs = await _scope!.Queryable<QuartzJobs>().Where(t => jobIds.Contains(t.Id)).ToListAsync();

            foreach (var job in jobs)
            {
                job.JobStatus = "start";

                await Scheduler!.ResumeJob(JobKey.Create(job.JobName!, job.JobCode!));
            }

            await _scope.Updateable(jobs).ExecuteCommandAsync();
        }

        /// <summary>
        /// 删除任务
        /// </summary>
        /// <param name="jobName"></param>
        /// <param name="jobCode"></param>
        public static async void DeleteJob(List<string> jobIds)
        {
            var jobs = await _scope!.Queryable<QuartzJobs>().Where(t => jobIds.Contains(t.Id)).ToListAsync();

            foreach (var job in jobs)
            {
                job.JobStatus = "stop";

                await Scheduler!.DeleteJob(JobKey.Create(job.JobName!, job.JobCode!));
            }

            await _scope.Deleteable(jobs).ExecuteCommandAsync();
        }
        #endregion

        #region Startup

        /// <summary>
        /// Startup
        /// </summary>
        /// <param name="dbType"></param>
        /// <param name="connectStr"></param>
        public static void AddQuartzerStartup(SqlSugar.DbType dbType, string connectStr)
        {
            PersistenceDb(dbType, connectStr);

            Init(dbType, connectStr);
        }

        /// <summary>
        /// Startup
        /// </summary>
        /// <param name="scope"></param>
        public static void AddQuartzerStartup(ISqlSugarClient scope)
        {
            PersistenceDb(scope);

            Init(scope.CurrentConnectionConfig.DbType, scope.CurrentConnectionConfig.ConnectionString);
        }

        #endregion
    }
}
